package visitor;

import java.util.Hashtable;
import java.util.Vector;

import syntaxtree.*;
import temp.*;
import translate.Frag;
import translate.FragList;
import tree.*;
import tree.Exp;
import tree.ExpList;


public class IRTreeCreator implements IRVisitor {

	private Vector<Frag> lista;
	private Frag procFrag;
	private Hashtable<String,temp.Temp> mapVarToTemp;
	private String currentMethod;
	private String currentClass;
	private String currentId;
	
	public IRTreeCreator(){
		lista = new Vector<Frag>();
		mapVarToTemp = new Hashtable<String,temp.Temp>();
		currentMethod = null;
	}
	
	public FragList getFragList(){
		return Frag.list;
	}
	
	// MainClass m;
	// ClassDeclList cl;
	public tree.Exp visit(Program n) {
		//TODO
		n.m.accept(this);
		for ( int i = 0; i < n.cl.size(); i++ ) {
			n.cl.elementAt(i).accept(this);
		}

		return null;
	}

	// Identifier i1,i2;
	// Statement s;
	public tree.Exp visit(MainClass n) {
		//TODO
		currentClass = n.i1.toString();
		// Colocar o parâmetro de main num temp
		getVar(n.i2.toString());	
		
		Frag novoFrag = Frag.newFrag(new Label(new String("main")),null);
		novoFrag.irtree = n.s.accept(this);
		novoFrag.name = "main";
		
		n.s.accept(this);
		return null;
	}



	// Identifier i;
	// VarDeclList vl;
	// MethodDeclList ml;
	public tree.Exp visit(ClassDeclSimple n) {
		//TODO
		currentClass = n.i.toString();
		for ( int i = 0; i < n.vl.size(); i++ ) {
			n.vl.elementAt(i).accept(this);
		}
		for ( int i = 0; i < n.ml.size(); i++ ) {
			n.ml.elementAt(i).accept(this);
		}
		return null;
	}



	// Identifier i;
	// Identifier j;
	// VarDeclList vl;
	// MethodDeclList ml;
	public tree.Exp visit(ClassDeclExtends n) {
		currentClass = n.i.toString();
		for ( int i = 0; i < n.ml.size(); i++ ) {
			n.ml.elementAt(i).accept(this);
		}
		return null;
	}



	// Type t;
	// Identifier i;
	public tree.Exp visit(VarDecl n) {
		getVar(n.i.toString());
		return null;
	}

	// Type t;
	// Identifier i;
	// FormalList fl;
	// VarDeclList vl;
	// StatementList sl;
	// Exp e;
	public tree.Exp visit(MethodDecl n) {
		
		currentMethod = n.i.toString();
		
		for ( int i = 0; i < n.fl.size(); i++ ) {
			n.fl.elementAt(i).accept(this);
		}
		
		
		for ( int i = 0; i < n.vl.size(); i++ ) {
			n.vl.elementAt(i).accept(this);
		}
		
		// Criando os comandos como um conjunto de seqs
		tree.Exp stm = null;
		tree.Exp stm1 = null;
		tree.Exp stm2 = null;
		tree.SEQ seq = null;		
		
		Vector<tree.Exp> exps = new Vector<tree.Exp>();
		
		for ( int i = 0; i < n.sl.size(); i++ ) {
				exps.add(n.sl.elementAt(i).accept(this));
		}
		
		if( exps.size() > 1 ){
		while(exps.size()>1){
			stm2 = exps.lastElement();
			stm1 = exps.lastElement();
			seq = new SEQ(stm1,stm2);
		}
		
		if(exps.size() >0){
			stm1 = exps.lastElement();
			seq = new SEQ(stm1,seq);
		}
			stm = seq;
		}else{
			stm = exps.lastElement();
		}
				
		
		// tree.exp
		tree.Stm stmexp = n.e.accept(this);
		
		// Cria o método como um todo na forma de ESEQ
		tree.ESEQ method = new tree.ESEQ(stm,stmexp);
			
		// Coloca o novo fragmento na lista, juntamente com a sua IR tree, seu label e seu frame
		Frag novoFrag = Frag.newFrag(new Label(new String(currentMethod)),null);
		novoFrag.irtree = method;
		novoFrag.name = new String(currentMethod); 
		return null;
	}



	// Type t;
	// Identifier i;
	public tree.Exp visit(Formal n) {
		//TODO
		getVar(n.i.toString());
		return null;
	}

	public tree.Exp visit(IntArrayType n) {
		return null;
	}

	public tree.Exp visit(BooleanType n) {
		return null;
	}

	public tree.Exp visit(IntegerType n) {
		return null;
	}


	// String s;
	public tree.Exp visit(IdentifierType n) {
		return null;
	}


	// StatementList sl;
	public tree.Exp visit(Block n) {
		tree.Exp stm = null;
		tree.Exp stm1 = null;
		tree.Exp stm2 = null;
		tree.SEQ seq = null;		
		
		for ( int i = 0; i < n.sl.size(); i++ ) {
			if( i%2 == 0 ){
				stm2 = n.sl.elementAt(i).accept(this);
				seq = new tree.SEQ(stm1,stm2);
				stm = seq;
			}else{
				stm1 = n.sl.elementAt(i).accept(this);
				stm = stm1;
			}
		}

		return (tree.Exp)stm;
	}


	// Exp e;
	// Statement s1,s2;
	public tree.Exp visit(If n) {
		tree.Exp e = n.e.accept(this);		
		tree.Exp s1 = n.s1.accept(this);
		tree.Exp s2 = n.s2.accept(this);
		Label f = new Label();
		Label t = new Label();
		Label end = new Label();
		
		return new SEQ(new CJUMP(CJUMP.EQ, new CONST(1), e, t, f),
		       new SEQ( new LABEL(t), 
				new SEQ ( s1,
					new SEQ ( new JUMP(end),
						new SEQ ( new LABEL(f),
							new SEQ (s2, 
								new LABEL(end)
								))))));	
	}

	// Exp e;
	// Statement s;
	public tree.Exp visit(While n){		
		n.e.accept(this);		
		tree.Exp expression = n.e.accept(this);
		tree.Exp bloco = n.s.accept(this);
		Label t = new Label();
		Label f = new Label();
		Label s = new Label();		
		return new SEQ(new LABEL(s), 
				 new SEQ(new CJUMP(CJUMP.EQ, new CONST(1), expression, t, f), 
				 		new SEQ(new LABEL(t),
				 				new SEQ(bloco, 										
				  						new SEQ(new JUMP(s), 
					 							new LABEL(f))))));
		}


	// Exp e;
	public tree.Exp visit(syntaxtree.Print n) {
		//TODO
		tree.ExpList explist = new ExpList(n.e.accept(this),null);
		return new CALL(new NAME(new Label(new String("PRINT"))),explist);
	}


	// Identifier i;
	// Exp e;
	public tree.Exp visit(Assign n) {		
		//TODO	
		return new MOVE(new TEMP(getVar(n.i.toString())),n.e.accept(this));
	}


	// Identifier i;
	// Exp e1,e2;
	public tree.Exp visit(ArrayAssign n) {
		//TODO

		return new MOVE( new BINOP(BINOP.PLUS,
									new MEM(new TEMP(getVar(n.i.toString()))), 
									n.e1.accept(this)) ,
				n.e2.accept(this) );
	}



	// Exp e1,e2;
	public tree.Exp visit(And n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e2.accept(this);               

		return new BINOP(BINOP.AND,op1,op2);
	}

	// Exp e1,e2;
	public tree.Exp visit(Plus n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e2.accept(this);

		return new BINOP(BINOP.PLUS, op1, op2);
	}


	// Exp e1,e2;
	public tree.Exp visit(Minus n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e1.accept(this);

		return  new BINOP(BINOP.MINUS, op1, op2);
	}



	// Exp e1,e2;
	public tree.Exp visit(Times n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e1.accept(this);

		return new BINOP(BINOP.MUL, op1, op2);
	}


	// Exp e1,e2;
	public tree.Exp visit(ArrayLookup n) {
		return new MEM( new BINOP(BINOP.PLUS,
					new MEM(n.e1.accept(this)),
					n.e2.accept(this)));
	}

	// Exp e;
	public tree.Exp visit(ArrayLength n) {
		return new CALL(new NAME(new Label("LENGTH")), 
				new ExpList( new TEMP(getVar(currentId)) , new ExpList( n.e.accept(this) , null ) ));
	}


	// Exp e;
	// Identifier i;
	// ExpList el;
	public tree.Exp visit(Call n) {
		tree.ExpList list = null;
		tree.Exp node;			 
		for ( int i = 0; i < n.el.size(); i++ ) {
			node = n.el.elementAt(i).accept(this);
			list = new ExpList(node,list);
		}		
		return new tree.CALL(new NAME(new Label(n.i.toString())), list );
	}


	// int i;
	public tree.Exp visit(IntegerLiteral n) {		
		return new CONST(n.i);
	}


	public tree.Exp visit(True n) {
		return new CONST(1);
	}


	public tree.Exp visit(False n) {
		return new CONST(0);
	}


	// String s;
	public tree.Exp visit(IdentifierExp n) {
		currentId = n.s;		
		return new TEMP(getVar(currentId));
	}


	public tree.Exp visit(This n) {
		//TODO
		currentId = "this";
		return new TEMP(getVar(currentId));
	}


	// Exp e;
	public tree.Exp visit(NewArray n) {		
		return new TEMP(new temp.Temp());
	}


	// Identifier i;
	public tree.Exp visit(NewObject n) {
		return new TEMP(new temp.Temp());
	}

	// Exp e;
	public tree.Exp visit(Not n){
		tree.Exp elemento = n.e.accept(this);
		tree.Exp not = new BINOP(BINOP.MINUS, new CONST(1), elemento );
		return not;
	}


	// String s;
	public tree.Exp visit(Identifier n){
		//TODO	
		//return new NAME(new Label(n.s));
		return new TEMP(getVar(n.s));
	}


	// Exp e1,e2;
	public tree.Exp visit(LessThan n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e2.accept(this);
		TEMP r = new TEMP(new temp.Temp());
		Label t = new temp.Label();
		Label f = new temp.Label();
		Label e = new temp.Label();		
		return new ESEQ(				
				new SEQ(new tree.CJUMP(CJUMP.LT,op1, op2, t, f),			
						new SEQ(new SEQ(new LABEL(t),new SEQ(new MOVE(r, new CONST(1)),
						new JUMP(e))),new SEQ(new LABEL(f),
								new SEQ(new MOVE(r, new CONST(0)),
								new SEQ(new tree.JUMP(e),new LABEL(e)))))), r);		

	}

	//// Visitors NOVOS
	public tree.Exp visit(GreaterThan n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e2.accept(this);
		TEMP r = new TEMP(new temp.Temp());
		Label t = new temp.Label();
		Label f = new temp.Label();
		Label e = new temp.Label();		
		return new ESEQ(				
				new SEQ(new tree.CJUMP(CJUMP.GT,op1, op2, t, f),			
						new SEQ(new SEQ(new LABEL(t),new SEQ(new MOVE(r, new CONST(1)),
						new JUMP(e))),new SEQ(new LABEL(f),
								new SEQ(new MOVE(r, new CONST(0)),
								new SEQ(new tree.JUMP(e),new LABEL(e)))))), r);		

	}

	public tree.Exp visit(GreaterEqualThan n) {		
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e2.accept(this);
		TEMP r = new TEMP(new temp.Temp());
		Label t = new temp.Label();
		Label f = new temp.Label();
		Label e = new temp.Label();		
		return new ESEQ(				
				new SEQ(new tree.CJUMP(CJUMP.GE,op1, op2, t, f),			
						new SEQ(new SEQ(new LABEL(t),new SEQ(new MOVE(r, new CONST(1)),
						new JUMP(e))),new SEQ(new LABEL(f),
								new SEQ(new MOVE(r, new CONST(0)),
								new SEQ(new tree.JUMP(e),new LABEL(e)))))), r);		
		
	}

	public tree.Exp visit(LessEqualThan n) {
		tree.Exp op1 = n.e1.accept(this);
		tree.Exp op2 = n.e2.accept(this);
		TEMP r = new TEMP(new temp.Temp());
		Label t = new temp.Label();
		Label f = new temp.Label();
		Label e = new temp.Label();		
		return new ESEQ(				
				new SEQ(new tree.CJUMP(CJUMP.LE,op1, op2, t, f),			
						new SEQ(new SEQ(new LABEL(t),new SEQ(new MOVE(r, new CONST(1)),
						new JUMP(e))),new SEQ(new LABEL(f),
								new SEQ(new MOVE(r, new CONST(0)),
								new SEQ(new tree.JUMP(e),new LABEL(e)))))), r);		
		
	}
	
	public temp.Temp getVar(String id){
		temp.Temp t = mapVarToTemp.get(id);
		if(t == null){
			t = new temp.Temp();
			mapVarToTemp.put(id,t);		
		}
		return t;
	}
}